namespace Maple.Branch.AuthServer.DataSeeds
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using IdentityServer.DataSeeds;
    using IdentityServer.IRepositories;
    using IdentityServer4;
    using IdentityServer4.Models;
    using Maple.Branch.AuthServer.Configurations;
    using Microsoft.Extensions.Configuration;
    using Permissions.DataSeed;
    using Volo.Abp.Authorization.Permissions;
    using Volo.Abp.Data;
    using Volo.Abp.DependencyInjection;
    using Volo.Abp.Guids;
    using Volo.Abp.Uow;
    using ApiResource = IdentityServer.Entities.ApiResource;
    using Client = IdentityServer.Entities.Client;
    using Secret = IdentityServer4.Models.Secret;

    public class AuthServerDataSeeder : IDataSeedContributor, ITransientDependency
    {
        private readonly IApiResourceRepository _apiResourceRepository;
        private readonly IClientRepository _clientRepository;
        private readonly IIdentityServerDataSeeder _identityServerDataSeeder;
        private readonly IGuidGenerator _guidGenerator;
        private readonly IPermissionsDataSeeder _permissionDataSeeder;
        private readonly AuthServerConfiguration _authServerConfig;

        public AuthServerDataSeeder(
            IClientRepository clientRepository,
            IApiResourceRepository apiResourceRepository,
            IIdentityServerDataSeeder identityServerDataSeeder,
            IGuidGenerator guidGenerator,
            IPermissionsDataSeeder permissionDataSeeder,
            AuthServerConfiguration authServerConfig)
        {
            _clientRepository = clientRepository;
            _apiResourceRepository = apiResourceRepository;
            _identityServerDataSeeder = identityServerDataSeeder;
            _guidGenerator = guidGenerator;
            _permissionDataSeeder = permissionDataSeeder;
            _authServerConfig = authServerConfig;
        }

        [UnitOfWork]
        public virtual async Task SeedAsync(DataSeedContext context)
        {
            await _identityServerDataSeeder.CreateStandardResourcesAsync();

            await CreateApiResourcesAsync();

            await CreateClientsAsync();
        }

        private async Task CreateApiResourcesAsync()
        {
            var commonApiUserClaims = new[]
               {
                "email",
                "email_verified",
                "name",
                "role"
            };

            foreach (var item in _authServerConfig.ApiResources.Where(m => !m.IsNullOrEmpty()).Distinct())
            {
                await CreateApiResourceAsync(item, commonApiUserClaims);
            }
        }

        private async Task CreateClientsAsync()
        {
            foreach (var item in _authServerConfig.Clients)
            {
                await CreateClientAsync(
                    id: item.ClientId,
                    scopes: item.Scopes,
                    grantTypes: item.GrantTypes,
                    secret: new Secret(item.Secret.Sha256()).Value,
                    redirectUri: item.RedirectUri,
                    postLogoutRedirectUri: item.postLogoutRedirectUri,
                    permissions: item.Permissions);
            }
        }

        private async Task<Client> CreateClientAsync(
            string id,
            IEnumerable<string> scopes,
            ICollection<string> grantTypes,
            string secret,
            string description= null,
            string redirectUri = null,
            string postLogoutRedirectUri = null,
            IEnumerable<string> permissions = null)
        {
            var client = await _clientRepository.FindByCliendIdAsync(id);
            if (client == null)
            {
                client = await _clientRepository.InsertAsync(
                    new Client(
                        _guidGenerator.Create(),
                        id
                    )
                    {
                        ClientName = id + " Client",
                        ProtocolType = "oidc",
                        Description = description,
                        AlwaysIncludeUserClaimsInIdToken = true,
                        AllowOfflineAccess = true,
                        AbsoluteRefreshTokenLifetime = 60 * 60 * 24 * 365, // = 31536000s / 365 days
                        AccessTokenLifetime = 60 * 60 * 24 * 365, // = 31536000s / 365 days
                        AuthorizationCodeLifetime = 60 * 5, // = 300s / 5min
                        IdentityTokenLifetime = 60 * 5, // = 300s / 5min
                        RequireConsent = false
                    },
                    autoSave: true
                );
            }

            foreach (var scope in scopes)
            {
                if (client.FindScope(scope) == null)
                {
                    client.AddScope(scope);
                }
            }

            foreach (var grantType in grantTypes)
            {
                if (client.FindGrantType(grantType) == null)
                {
                    client.AddGrantType(grantType);
                }
            }

            if (client.FindSecret(secret) == null)
            {
                client.AddSecret(secret);
            }

            if (redirectUri != null)
            {
                if (client.FindRedirectUri(redirectUri) == null)
                {
                    client.AddRedirectUri(redirectUri);
                }
            }

            if (postLogoutRedirectUri != null)
            {
                if (client.FindPostLogoutRedirectUri(postLogoutRedirectUri) == null)
                {
                    client.AddPostLogoutRedirectUri(postLogoutRedirectUri);
                }
            }

            if (permissions != null)
            {
                await _permissionDataSeeder.SeedAsync(
                    ClientPermissionValueProvider.ProviderName,
                    id,
                    permissions
                );
            }

            return await _clientRepository.UpdateAsync(client);
        }

        private async Task<ApiResource> CreateApiResourceAsync(string name, IEnumerable<string> claims)
        {
            var apiResource = await _apiResourceRepository.FindByNameAsync(name);
            if (apiResource == null)
            {
                apiResource = await _apiResourceRepository.InsertAsync(
                    new ApiResource(
                        _guidGenerator.Create(),
                        name,
                        name + " API"
                    ),
                    autoSave: true
                );
            }

            foreach (var claim in claims)
            {
                if (apiResource.FindClaim(claim) == null)
                {
                    apiResource.AddUserClaim(claim);
                }
            }

            return await _apiResourceRepository.UpdateAsync(apiResource);
        }
    }
}
